package contract.jvm;

import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import contract.jvm.instructions.*;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodNode;

import java.io.InputStream;

/**
 * 一个简单的JVM
 */
public class Jvm {

    /**
     * 程序计数器（pc）
     */
    public final ProgramCounter pc = new ProgramCounter();

    /**
     * 虚拟机栈
     */
    public final JvmStack jvmStack = new JvmStack();

    /**
     * 堆
     */
    public final Heap heap = new Heap();

    /**
     * 方法区
     */
    public final MethodArea methodArea = new MethodArea();

    /**
     * 新建对象，运行默认构造方法
     */
    public Reference newObject(String className) {
        // 加载class
        loadClass(className);
        // 新建引用
        Reference reference = newReference(className);
        // 运行默认构造方法
        runMethod(reference, "<init>", "()V", null);
        return reference;
    }

    /**
     * 加载class，使用ASM解析class文件，保存解析后的结果
     */
    public void loadClass(String className) {
        ClassNode classNode = methodArea.getClass(className);
        if (classNode == null) {
            InputStream classStream = ClassLoader.getSystemResourceAsStream(
                    className.replace('.', '/') + ".class");
            if (classStream == null) {
                throw new RuntimeException("Class Not Found");
            }
            byte[] classFile = IoUtil.readBytes(classStream);
            classNode = new ClassNode();
            ClassReader classReader = new ClassReader(classFile);
            classReader.accept(classNode, 0);
            methodArea.saveClass(classNode);
            if (StrUtil.isNotEmpty(classNode.superName)) {
                loadClass(classNode.superName);
            }
        }
    }

    /**
     * 引用计数器
     */
    private int referenceCounter;

    /**
     * 新建引用
     */
    public Reference newReference(String className) {
        return new Reference(className, referenceCounter++);
    }

    /**
     * 运行方法
     */
    public void runMethod(Reference reference, String methodName, String methodDesc, Object[] methodArgs) {
        // 在实例方法调用中，局部变量0用于传递对象的引用
        methodArgs = ArrayUtil.insert(methodArgs, 0, reference);
        runMethod(reference.className, methodName, methodDesc, methodArgs);
    }

    /**
     * 运行方法
     */
    public void runMethod(String className, String methodName, String methodDesc, Object[] methodArgs) {
        // 找到方法
        MethodNode methodNode = methodArea.getMethod(className, methodName, methodDesc);
        if (methodNode == null) {
            throw new RuntimeException("Method Not Found");
        }
        // 新建栈帧
        Frame frame = new Frame(methodNode, methodArgs);
        // 把栈帧推入虚拟机栈
        jvmStack.push(frame);
        // 如果是native方法，先调用native方法的实现
        if (NativeMethod.isNativeMethod(methodNode)) {
            NativeMethod.runNativeMethod(this, className, methodName, methodDesc);
        }
        // 运行当前栈帧
        runCurrentFrame();
    }

    /**
     * 运行当前栈帧
     */
    private void runCurrentFrame() {
        // 当前栈帧
        Frame currentFrame = jvmStack.lastElement();
        // 当前指令指向当前方法的第一个指令
        pc.currentInsnNode = currentFrame.methodNode.instructions.getFirst();
        // 循环运行字节码，当栈帧有返回或者没有下一个字节码，结束循环
        while (!currentFrame.returned && pc.nextOpCode() != null) {
            runOpCode();
        }
        // 虚拟机栈弹出当前栈帧
        jvmStack.pop();
        // 如果当前栈帧有返回值，把值传递给前一栈帧
        if (!jvmStack.isEmpty() && currentFrame.returned && !currentFrame.returnVoid) {
            jvmStack.lastElement().operandStack.push(currentFrame.returnValue);
        }
    }

    /**
     * 运行字节码
     */
    private void runOpCode() {
        OpCode opCode = pc.currentOpCode();
        switch (opCode) {
            case ILOAD:
                Iload.iload(this);
                break;
            case ALOAD:
                Aload.aload(this);
                break;
            case IRETURN:
                Return.ireturn(this);
                break;
            case RETURN:
                Return.return_(this);
                break;
            case GETFIELD:
                Getfield.getfield(this);
                break;
            case PUTFIELD:
                Putfield.putfield(this);
                break;
            case INVOKEVIRTUAL:
                InvokeMethod.invokevirtual(this);
                break;
            case INVOKESPECIAL:
                InvokeMethod.invokespecial(this);
                break;
            case INVOKESTATIC:
                InvokeMethod.invokestatic(this);
                break;
            default:
                System.out.println(opCode + " 没有实现");
                break;
        }
    }

}
